package ru.nsu.ccfit.kalah.game;

import java.util.ArrayList;
import java.util.List;

public class KalahController {
    /**
     * Extra-turn series points cache
     */
    private static int lookupTable[] = {0, 2, 2, 6, 2, 6, 6, 12, 2, 6, 6, 8, 6, 8, 8,
            14, 2, 4, 4, 8, 4, 8, 8, 14, 4, 8, 8, 10, 8, 10, 10, 16, 1, 3, 3,
            7, 3, 7, 7, 13, 3, 7, 7, 9, 7, 9, 9, 15, 3, 5, 5, 9, 5, 9, 9, 15,
            5, 9, 9, 11, 9, 11, 11, 17};
    private Field field;

    protected PlayerType currentPlayer = PlayerType.SOUTH;

    public KalahController() {

        // SOUTH is first

    }

    public KalahController(Field field) {
        this.field = field;
        // SOUTH is first

    }

    public KalahController(KalahController controller) {
        this.field = new Field(controller.field);
        this.currentPlayer = controller.currentPlayer;
    }


    public Field getField() {
        return field;
    }

    public void setField(Field field) {
        this.field = field;
    }

    List<Boolean> playerMove = new ArrayList<Boolean>();

    private HistoryBuffer<PlayerType> historyBuffer;


    private void changePlayer() {
        currentPlayer = currentPlayer == PlayerType.SOUTH ? PlayerType.NORTH : PlayerType.SOUTH;
    }

    private HistoryBuffer<PlayerType> getHistoryBuffer() {
        if (historyBuffer == null) {
            historyBuffer = new HistoryBuffer<PlayerType>();
        }
        return historyBuffer;
    }

    public void move(int pos) {
        field.save();
//        playerMove.add(player);
        getHistoryBuffer().push(currentPlayer);
        int[] pits = field.pits;
        int offset = ((currentPlayer == PlayerType.SOUTH) ? Field.SOUTH_PLAYER_HOUSE : Field.NORTH_PLAYER_HOUSE);
        pos += offset;
        int s = pits[pos], c = pos;
        pits[pos] = 0;

        while (--s > 0) {
            if (++c == Field.PITS_COUNT - offset)
                ++c;
            if (c > 13)
                c -= Field.PITS_COUNT;
            ++pits[c];
        }

        if (++c == Field.PITS_COUNT - offset)
            ++c;
        if (c > Field.PITS_COUNT - 1)
            c -= Field.PITS_COUNT;
        if (c != 7 - offset)
            changePlayer();
        if (((c - offset) > 0 && (c - offset) < 7) && pits[c] == 0
                && pits[Field.PITS_COUNT - c] != 0) {
            ++pits[c];
            pits[7 - offset] += pits[c] + pits[Field.PITS_COUNT - c];
            pits[c] = pits[Field.PITS_COUNT - c] = 0;
        } else
            ++pits[c];
    }

    public void moveWithAnimation(int pos, List<AbstractMoveAnimation> abstractMoves) {
        field.save();
//        playerMove.add(player);
        getHistoryBuffer().push(currentPlayer);
        int[] pits = field.pits;
        int offset = ((currentPlayer == PlayerType.SOUTH) ? Field.SOUTH_PLAYER_HOUSE : Field.NORTH_PLAYER_HOUSE);
        pos += offset;
        int s = pits[pos], c = pos;
        pits[pos] = 0;

        while (--s > 0) {
            if (++c == Field.PITS_COUNT - offset) {
                ++c;

            }
            if (c > 13)
                c -= Field.PITS_COUNT;
            ++pits[c];
            abstractMoves.add(new ShiftOneStoneAnimation(pos, c));
        }

        if (++c == Field.PITS_COUNT - offset) {
            ++c;

        }
        if (c > Field.PITS_COUNT - 1)
            c -= Field.PITS_COUNT;

        if (c != 7 - offset)
            changePlayer();
        if (((c - offset) > 0 && (c - offset) < 7) && pits[c] == 0
                && pits[Field.PITS_COUNT - c] != 0) {
            ++pits[c];
            abstractMoves.add(new ShiftOneStoneAnimation(pos, c));
            pits[7 - offset] += pits[c] + pits[Field.PITS_COUNT - c];
            pits[c] = pits[Field.PITS_COUNT - c] = 0;

            abstractMoves.add(new ShiftAllStonesAnimation(Field.PITS_COUNT - c, c, 7 - offset));
        } else      {
            ++pits[c];
            abstractMoves.add(new ShiftOneStoneAnimation(pos, c));
        }

    }

    public void undoMove() {
        field.restore();
        currentPlayer = getHistoryBuffer().pop();
    }

    public int mark() {
        int[] pits = field.pits;
        int offset = ((currentPlayer == PlayerType.SOUTH) ? 0 : 7);

        int fm = 0;
        for (int i = 1; i <= 6; ++i) {
            if (pits[i + offset] == 7 - i) {
                fm |= (1 << (i - 1));
            }
        }

        int mark_cache = (pits[0] - pits[7]);
        mark_cache += (currentPlayer == PlayerType.SOUTH) ? -lookupTable[fm] : lookupTable[fm];

        return mark_cache;
    }

    public int getFirst() {
        int[] pits = field.pits;
       return (pits[1] + pits[2] + pits[3] + pits[4] + pits[5] + pits[6]) + pits[7];
    }
    public int getSecond() {
        int[] pits = field.pits;
        return (pits[8] + pits[9] + pits[10] + pits[11] + pits[12] + pits[13])+ pits[0];
    }
    public boolean terminal() {
        int[] pits = field.pits;
        int a = (pits[1] + pits[2] + pits[3] + pits[4] + pits[5] + pits[6]);
        int b = (pits[8] + pits[9] + pits[10] + pits[11] + pits[12] + pits[13]);
        if (a == 0) {
            pits[0] += b;
            pits[8] = pits[9] = pits[10] = pits[11] = pits[12] = pits[13] = 0;
            return true;
        } else if (b == 0) {
            pits[7] += a;
            pits[1] = pits[2] = pits[3] = pits[4] = pits[5] = pits[6] = 0;
            return true;
        }
        return false;
    }

    public boolean allow(int index) {
        int[] pits = field.pits;
        return (currentPlayer == PlayerType.SOUTH && pits[index] != 0) || (currentPlayer == PlayerType.NORTH && pits[index + 7] != 0);
    }

}
